{ "cells": [ { "cell_type": "markdown", "id": "89f0eb3a-044d-4337-9cef-43a41cbef37c", "metadata": {}, "source": [ "# ANISE\n", "\n", "ANISE is a modern rewrite of NAIF SPICE, written in Rust and providing interfaces to other languages including Python.\n", "\n", "## Goal\n", "\n", "By the end of this tutorial, you will know how to use your custom frame kernels (FK) and text planetary constant kernels (TPC) in ANISE.\n", "\n", "Let's start by installing ANISE: `pip install anise`" ] }, { "cell_type": "markdown", "id": "1ee5f04e-762a-41d7-8d3b-ac537ae7e10c", "metadata": {}, "source": [ "## Data structure\n", "\n", "SPICE supports text-based kernels. These allow for easy modification and inclusion of documentation directly in the text file. The way SPICE handles these is by keeping a file handler open on the file while it's loaded. This causes a number of issues, such as potential file locking problems, increased risk of data corruption in cases of unexpected software termination, and increased difficulty in managing concurrent access, especially pertinent in the context of onboard flight software. To prevent these issues, ANISE maintains a pointer to the data contained in these files in memory (via a memory mapping or a heap allocation). Moreover, ANISE ensures that the data it uses allows for immediate random access, which is a method of accessing data at any point in memory with equal speed, independent of its location, thereby speeding up searches within the kernel data. To facilitate this, the data is stored in a platform-independent binary structure using the ASN.1 DER specification (the telecommunications industry standard for 20+ years). ASN.1 offers advantages like well-defined data structures, robust encoding schemes, and ease of interoperability across different platforms, making it a reliable choice for storing kernel data.\n", "\n", "This approach allows ANISE to parse through the FK and TPC equivalents significantly faster than SPICE, while storing the same information.\n", "\n", "However, this also means that ANISE cannot load the text files directly. Instead, these must be converted into the ANISE format, respectively PCA and EPA for \"Planetary Constants ANISE\" kernel and \"Euler Parameter ANISE\" kernel. Euler parameters (also known as quaternion parameters) offer a compact and non-redundant representation of orientations in three dimensions. They differ from quaternions in their normalization constraint and are slightly more robust to numerical errors in some computational scenarios.\n", "\n", "For details about the data set structure, refer to the API documentation: [`DataSet`](https://docs.rs/anise/latest/anise/structure/dataset/struct.DataSet.html). The `EulerParameterDataSet` and the `PlanetaryDataSet` are concrete implementations of this `DataSet` structure.\n", "\n", "### Version compatibility\n", "\n", "ANISE guarantees not to change the structure of these kernels between patch versions (e.g., version `0.3.0` and version `0.3.99` are guaranteed to have compatible kernels). However, until version `1.0.0`, the structure _may_ change and if so, the updated version of the default PCA and EPA files will be added to the Nyx Space cloud.\n", "\n", "Since version `0.1.0`, the structure of the kernels has _not_ changed. However, the ANISE version is encoded at the start of each kernel. This is only used if the data set cannot be properly decoded to inform the user of the expected ANISE version and the one that they're trying to load. In other words, although there is a version `0.3` of the PCK08 and PCK11 kernels, the files used in version `0.1.0` are still compatible." ] }, { "cell_type": "markdown", "id": "c0233d36-b01b-472b-9880-d1119099cc7c", "metadata": {}, "source": [ "## Planetary Constant ANISE kernels\n", "\n", "Planetary Constant ANISE (PCA) kernels (or a \"data set\") include a lookup table for random access via a name or an ID, metadata, and, more importantly, the actual planetary data itself. This data includes gravitational parameters, the shape of the triaxial ellipsoid, phase angle polynomials for the prime meridian, pole right ascension and declination, and more. You'll find all of the specifications in the API documentation: [`PlanetaryData`](https://docs.rs/anise/latest/anise/structure/planetocentric/struct.PlanetaryData.html).\n", "\n", "In the previous tutorials, we focused on fetching the frame information from the Almanac. This operation reads the PCA to return a copy of this information. This is why a PCA is provided in the `latest.dhall` configuration file for an Almanac.\n", "\n", "The Planetary Data structure includes the gravitational data. _However_, the SPICE TPC files contain _either_ ellipsoid definition information _or_ gravitational data. Therefore, to build a PCA, ANISE requires _both_ versions of the TPCs.\n", "\n", "Let's go ahead and build a PCA file from the NAIF `pck0008.tpc` and the `gm_de431.tpc` files." ] }, { "cell_type": "code", "execution_count": 1, "id": "d8f0c21e-5075-4179-a3da-d85d35078709", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[0;31mSignature:\u001b[0m \u001b[0mconvert_tpc\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mpck_file_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mgm_file_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0manise_output_path\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0moverwrite\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m\n", "Converts two KPL/TPC files, one defining the planetary constants as text, and the other defining the gravity parameters, into the PlanetaryDataSet equivalent ANISE file.\n", "KPL/TPC files must be converted into \"PCA\" (Planetary Constant ANISE) files before being loaded into ANISE.\n", "\u001b[0;31mType:\u001b[0m builtin_function_or_method" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from anise.utils import convert_tpc\n", "convert_tpc?" ] }, { "cell_type": "code", "execution_count": 2, "id": "4f28e5f6-a768-4798-be37-a922948423d8", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Skipping 802: no gravity data\n", "Skipping 806: no gravity data\n", "Skipping 9511010: no gravity data\n", "Skipping 514: no gravity data\n", "Skipping 714: no gravity data\n", "Skipping 509: no gravity data\n", "Skipping 707: no gravity data\n", "Skipping 711: no gravity data\n", "Skipping 804: no gravity data\n", "Skipping 2431010: no gravity data\n", "Skipping 506: no gravity data\n", "Skipping 710: no gravity data\n", "Skipping 712: no gravity data\n", "Skipping 612: no gravity data\n", "Skipping 618: no gravity data\n", "Skipping 713: no gravity data\n", "Skipping 803: no gravity data\n", "Skipping 508: no gravity data\n", "Skipping 515: no gravity data\n", "Skipping 715: no gravity data\n", "Skipping 511: no gravity data\n", "Skipping 706: no gravity data\n", "Skipping 516: no gravity data\n", "Skipping 507: no gravity data\n", "Skipping 614: no gravity data\n", "Skipping 808: no gravity data\n", "Skipping 805: no gravity data\n", "Skipping 807: no gravity data\n", "Skipping 513: no gravity data\n", "Skipping 2000216: no gravity data\n", "Skipping 613: no gravity data\n", "Skipping 708: no gravity data\n", "Skipping 512: no gravity data\n", "Skipping 709: no gravity data\n", "Skipping 510: no gravity data\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Added 49 items\n" ] } ], "source": [ "convert_tpc(\"../../data/pck00008.tpc\", \"../../data/gm_de431.tpc\", \"demo08.pca\", True)" ] }, { "cell_type": "markdown", "id": "443e0da7-cebe-4dd6-97e6-bee3b8f0450c", "metadata": {}, "source": [ "We now have a PCA called `demo08.pca` which includes 49 entries. This file is compatible with _any_ machine you run ANISE on, little or big endian (which is _not_ the case of the DAF/BSP or DAF/BPC files).\n", "\n", "Let's load this file in an Almanac." ] }, { "cell_type": "code", "execution_count": 4, "id": "24fa8803-6aa6-493b-a887-89f6e5e2f29a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Almanac: #SPK = 0\t#BPC = 0\tPlanetaryData with 49 ID mappings and 0 name mappings (@0x559b4af30800)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from anise import Almanac\n", "almanac = Almanac(\"demo08.pca\")\n", "almanac" ] }, { "cell_type": "markdown", "id": "4524157c-6d4d-4acb-93d0-ed0c02fcc883", "metadata": {}, "source": [ "Since we don't have anything loaded other than these planetary constants, we can't do a whole lot, but we can query the Almanac for the shape and gravitational data by using `frame_info`." ] }, { "cell_type": "code", "execution_count": 10, "id": "ab2d1579-411e-4cdb-ab2c-19fb15292cb0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "body 599 IAU_JUPITER (μ = 126686534.9218008 km^3/s^2, eq. radius = 71492 km, polar radius = 66854 km, f = 0.0648743915403122) (@0x7fe814070310)" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from anise.astro.constants import Frames\n", "iau_jupiter_frame = almanac.frame_info(Frames.IAU_JUPITER_FRAME)\n", "iau_jupiter_frame" ] }, { "cell_type": "code", "execution_count": 13, "id": "0a9b1b53-dea9-4e91-939b-15c310b0a92f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "126686534.9218008" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "iau_jupiter_frame.mu_km3_s2()" ] }, { "cell_type": "markdown", "id": "1c627e1c-0e9a-4965-b4b6-afa83be211b0", "metadata": {}, "source": [ "### Exercise: Modifying the gravitational parameter of the Earth frame\n", "\n", "+ Make a copy of the `gm_de431.tpc` file and remove everything but the Earth GM.\n", "+ Set the Earth GM to the one used by GMAT: `398600.4415` km^3/s^2\n", "+ Make a copy of the `pck00008.tpc` file, removing everything except Earth related data.\n", "+ Build a new PCA file using these two new files\n", "+ Load a default Almanac and an empty Almanac where you'll load these two files into, along with the `DE440s.bsp` file.\n", "+ Query the Cartesian state of the Earth at any time of your choosing from both of these Almanac. The state should be the same, since it'll be from the DE440s.bsp.\n", "+ Observe how the frame graviational parameter information of both will differ.\n", "+ Finally, using the `at_epoch` function on both of these state, perform a two-body propagation for both and notice how the graviational parameter affects the result." ] }, { "cell_type": "markdown", "id": "652143bf-5066-4291-aac7-a663982333de", "metadata": {}, "source": [ "## Euler Parameter ANISE kernel\n", "\n", "Euler parameters are a normalized quaternion. In fact, in the ANISE code in Rust, `EulerParameter` is an alias for `Quaternion`. Euler parameters are useful for defining fixed rotations, e.g. the rotation from the Moon Principal Axes frame to the Moon Mean Earth frame.\n", "\n", "Euler Parameter ANISE kernels (EPA) can be used to store these fixed rotations between frames, and reference them either by their ID or by their name. **They are the equivalent of SPICE's `FK` text files.**\n", "\n", "Until [#175](https://github.com/nyx-space/anise/issues/175), rotation data is _not_ exposed to Python. \n", "\n", "### How to build EPA files\n", "\n", "However, it's possible to convert an FK file into the EPA file using the following function." ] }, { "cell_type": "code", "execution_count": 1, "id": "263507b1-8f58-44e5-b0b3-4abcf0a00b92", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "\u001b[0;31mSignature:\u001b[0m\n", "\u001b[0mconvert_fk\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m \u001b[0mfk_file_path\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m \u001b[0manise_output_path\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m \u001b[0mshow_comments\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m \u001b[0moverwrite\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mDocstring:\u001b[0m\n", "Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file.\n", "KPL/FK files must be converted into \"PCA\" (Planetary Constant ANISE) files before being loaded into ANISE.\n", "\u001b[0;31mType:\u001b[0m builtin_function_or_method" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from anise.utils import convert_fk\n", "convert_fk?" ] }, { "cell_type": "markdown", "id": "e26f92f9-9fa6-4fb7-b142-8bcaf0451b28", "metadata": {}, "source": [ "On the Nyx Space Cloud, you'll find the `moon.fk` file, which includes the mapping between the Moon PA and Moon ME frame. The latest Almanac also includes the high precision Moon ME frame. Hence, with default data, you can rotate an object from any frame into the high precision Moon ME frame.\n", "\n", "## Exercise\n", "\n", "Using tutorial 04, compute the azimuth, elevation, and range of the [Shackleton](https://en.wikipedia.org/wiki/Shackleton_(crater)) crater on the Moon to the city of Paris, France.\n", "\n", "Here are the general steps:\n", "\n", "1. Load the latest Almanac, and check (by printing it) that it includes both EPA and PCA data. Else, load the `moon_fk.epa` file from the Nyx Space Cloud using a `MetaFile` with the URL .\n", "2. Define a time series over a year with a granularity of 12 hours. This crater is on the South Pole of the Moon, and its visibility is often below the horizon of an object as far north as Paris.\n", "3. For each epoch, define Paris as an `Orbit` instance from its longitude and latitude (recall that the constants include the mean Earth angular rotation rate), in the IAU_EARTH frame. Also, build the crater in the IAU_MOON frame.\n", "4. Finally, call the `AER` function of the Almanac with each epoch to compute the AER data. Plot it!" ] }, { "cell_type": "code", "execution_count": null, "id": "0a91851b-188a-4dc8-9e8c-40276012dbd1", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": ".venv", "language": "python", "name": ".venv" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.4" } }, "nbformat": 4, "nbformat_minor": 5 }